package com.zzy.backend.consumer.utils;

import com.alibaba.fastjson.JSONObject;
import com.zzy.backend.consumer.WebSocketServer;
import com.zzy.backend.pojo.Bot;
import com.zzy.backend.pojo.Record;
import org.apache.tomcat.util.net.jsse.JSSEUtil;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author molimark<br />
 * @date: 2023/3/16 0:14<br/>
 * @description: <br/>
 */
public class Game extends Thread{
    private final Integer rows;
    private final Integer cols;
    private final Integer inner_walls_count;
    private final int[][] g;
    private final int[] dx=new int[]{1,0,-1,0};
    private final int[] dy=new int[]{0,1,0,-1};
    private final Player playerA,playerB;
    private Integer nextStepA = null;
    private Integer nextStepB = null;
    private ReentrantLock lock = new ReentrantLock();
    private String status = "playing"; // playing->finished
    private String loser = ""; // all:平局 A:A输 B:B输


    public Game(Integer rows,Integer cols,Integer inner_walls_count,Integer idA,Integer idB){
        this.rows = rows;
        this.cols = cols;
        this.inner_walls_count = inner_walls_count;
        this.g = new int[rows][cols];
        this.playerA = new Player(idA,this.rows-2,1,new ArrayList<>());
        this.playerB = new Player(idB,1,this.cols-2,new ArrayList<>());
    }

    public Player getPlayerA(){
        return playerA;
    }

    public Player getPlayerB(){
        return playerB;
    }

    public void setNextStepA(Integer nextStepA){
        lock.lock();
        try {
            this.nextStepA = nextStepA;
        }finally {
            lock.unlock();
        }
    }

    public void setNextStepB(Integer nextStepB){
        lock.lock();
        try {
            this.nextStepB = nextStepB;
        }finally {
            lock.unlock();
        }
    }

    public int[][] getG(){
        return g;
    }

    private void dfs(int x,int y,int[][] g){
        g[x][y]=1;
        for(int i=0;i<4;i++){
            int tx=x+dx[i],ty=y+dy[i];
            if(tx<1||tx>this.rows-2||ty<1||ty>this.cols-2)continue;
            if(g[tx][ty]==1)continue;
            dfs(tx,ty,g);
        }
    }

    private boolean check_connectivity(int[][] g){
        dfs(this.rows-2,1,g);
        for(int r=1;r<this.rows-1;r++){
            for(int c=1;c<this.cols-1;c++){
                if(g[r][c]==0)return false;
            }
        }
        return true;
    }

    private boolean draw(){ //画地图
        for(int i=0;i<this.rows;i++){
            for(int j=0;j<this.cols;j++){
                g[i][j]=0;
            }
        }

        for(int r=0;r<this.rows;r++){
            g[r][0]=g[r][this.cols-1]=1;
        }
        for(int c=0;c<this.cols;c++){
            g[0][c]=g[this.rows-1][c]=1;
        }

        Random random = new Random();
        for(int i=0;i<this.inner_walls_count/2;i++){
            for(int j=0;j<1000;j++){
                //返回0~this.rows-1当中的一个随机值
                int r=random.nextInt(this.rows);
                int c=random.nextInt(this.cols);
                if(g[r][c]==1)continue;
                if(r==this.rows-2&&c==1)continue;
                if(r==1&&c==this.cols-2)continue;
                g[r][c]=g[this.rows-1-r][this.cols-1-c]=1;
                break;
            }
        }

        int[][] copy_g = new int[g.length][];
        for(int i=0;i<g.length;i++){
            copy_g[i]=g[i].clone();
        }
        if(!check_connectivity(copy_g))return false;
        return true;
    }

    public void createMap(){
        for(int i=0;i<1000;i++){
            if(draw()){
                break;
            }
        }
    }

    private boolean nextStep(){
        /*
            //蛇每秒走5个格子
            this.speed = 5;

            因为前端设置的蛇每秒走5个格子，因此每个格子需要200ms，如果nextStep()函数
            执行的间隔小于200ms，那么就会导致先前的结果被覆盖掉，因此在run()函数中1000
            次循环结束后，可能格子还没有走满，而加上sleep(200)之后可以确保每次for循环
            都走一步，这样1000步之内蛇能够把格子占满
        */
        try {
            Thread.sleep(200);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        for(int i=0;i<100;i++){
            try {
                //sleep时间越小，循环次数越多，服务器压力越大，但是用户体验越好，因为每一步延迟小
                Thread.sleep(50);
                //这个加锁要放在for循环里面，如果放外面，那么在for循环中，nextStep不能被设置
                lock.lock();
                try {
                    if(nextStepA != null && nextStepB!=null){
                        playerA.getSteps().add(nextStepA);
                        playerB.getSteps().add(nextStepB);
                        return true;
                    }
                }finally {
                    lock.unlock();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        return false;
    }

    private boolean check_valid(List<Cell> cellsA,List<Cell> cellsB){
        int n = cellsA.size();
        Cell cell = cellsA.get(n-1);
        if(g[cell.getX()][cell.getY()]==1)return false;
        for(int i=0;i<n-1;i++){
            if(cellsA.get(i).getX() == cell.getX() && cellsA.get(i).getY() == cell.getY())
                return false;
        }
        // 这里不用循环到n是因为地图设置的行列和为奇数，因此两条蛇不可能同时走到同一格子上
        for(int i=0;i<n-1;i++){
            if(cellsB.get(i).getX() == cell.getX() && cellsB.get(i).getY() == cell.getY())
                return false;
        }

        return true;
    }

    private void judge(){ // 判断两名玩家下一步操作是否合法
        List<Cell> cellsA = playerA.getCells();
        List<Cell> cellsB = playerB.getCells();

        boolean validA = check_valid(cellsA,cellsB);
        boolean validB = check_valid(cellsB,cellsA);

        if(!validA||!validB){
            status = "finished";
            if(!validA&&!validB){
                loser = "all";
            }else if(!validA){
                loser = "A";
            }else{
                loser = "B";
            }
        }
    }

    private void sendAllMessage(String message){
        WebSocketServer.users.get(playerA.getId()).sendMessage(message);
        WebSocketServer.users.get(playerB.getId()).sendMessage(message);
    }

    private void sendMove(){
        lock.lock();
        try {
            JSONObject resp = new JSONObject();
            resp.put("event","move");
            resp.put("a_direction",nextStepA);
            resp.put("b_direction",nextStepB);
            nextStepA=nextStepB=null;
            sendAllMessage(resp.toJSONString());
        }finally {
            lock.unlock();
        }
    }

    private String getMapString(){
        StringBuilder res = new StringBuilder();
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                res.append(g[i][j]);
            }
        }
        return res.toString();
    }

    private void saveToDatabase(){
        Record record = new Record(
                null,
                playerA.getId(),
                playerA.getSx(),
                playerA.getSy(),
                playerB.getId(),
                playerB.getSx(),
                playerB.getSy(),
                playerA.getStepsString(),
                playerB.getStepsString(),
                getMapString(),
                loser,
                new Date()
        );

        WebSocketServer.recordMapper.insert(record);
    }

    private void sendResult(){ // 向两个client公布结果
        JSONObject resp = new JSONObject();
        resp.put("event","result");
        resp.put("loser",loser);
        saveToDatabase();
        sendAllMessage(resp.toJSONString());
    }

    @Override
    public void run() {
        //这里循环1000次是因为每3步蛇的长度+1，共有13*14个格子，所以需要循环13*14*3次，
        //为了保险起见循环1000次
        for(int i=0;i<1000;i++){
            if(nextStep()){ //是否获取了两条蛇的下一步操作
                judge();
                if(status.equals("playing")){
                    sendMove();
                }else{
                    sendResult();
                    break;
                }
            }else{
                status = "finished";
                lock.lock();
                try {
                    if(nextStepA==null&&nextStepB==null){
                        loser = "all";
                    }else if(nextStepA==null){
                        loser = "A";
                    }else{
                        loser = "B";
                    }
                }finally {
                    lock.unlock();
                }
                sendResult();
                break;
            }
        }
    }
}
